package jp.co.nes.awssupport.business.logic;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.bean.CampaignAssignedBean;
import jp.co.nes.awssupport.common.bean.CampaignGroupBean;
import jp.co.nes.awssupport.common.bean.CampaignPlanBean;
import jp.co.nes.awssupport.common.bean.ContractInformationBean;
import jp.co.nes.awssupport.common.bean.MonthlyCompiledBillingBean;
import jp.co.nes.awssupport.common.utility.DateUtil;

/**
 * キャンペーンプラン処理です。
 */
public class CampaignPlanProcessing {

    /** 処理対象年月 */
    private YearMonth targetYearMonth;

    /** 契約情報管理 */
    private ContractInformationManage ciManage;

    /**
     * 処理対象年月を引数にもつコンストラクタです。
     *
     * @param targetYearMonth 処理対象年月
     */
    public CampaignPlanProcessing(YearMonth targetYearMonth) {
        this.targetYearMonth = targetYearMonth;
        ciManage = new ContractInformationManage(targetYearMonth);
    }

    /**
     * キャンペーングループリストに含まれるキャンペーンプランを適用し、キャンペーン適用リストを返却します。<br>
     * なお、キャンペーン適用額は「係数 * 請求額(税抜) + 定数」で算出した結果を、小数第1位で四捨五入した値になります。<br>
     * 以下の場合、空リスト返却します。
     * <ul>
     * <li>キャンペーングループリストがnull or 空リストの場合</li>
     * <li>キャンペーングループリストに含まれるキャンペーングループに設定されているキャンペーンプランリストが全てnull or 空リストの場合</li>
     * </ul>
     * 【補足事項】
     * <ul>
     * <li>
     * 月次集計結果に含まれる請求額（税抜）は、小数第4位で四捨五入して小数第3位にした値の合計を、
     * 小数第1位で四捨五入して整数にした値である必要があります。
     * </li>
     * <li>
     * キャンペーングループリストには、契約情報と関連するものと、デフォルトのキャンペーングループが
     * 含まれている必要があります。
     * </li>
     * <li>
     * キャンペーンプランは、同一キャンペーングループID内の、同一製品名、同一使用タイプで、適用期間が重複しないように、
     * 1か月単位で登録します。
     * </li>
     * </ul>
     *
     * @param mcbBean 月次集計結果
     * @return キャンペーン適用リスト
     */
    public List<CampaignAssignedBean> apply(MonthlyCompiledBillingBean mcbBean) {
        String linkedAccountId = mcbBean.getLinkedAccountId();
        ContractInformationBean ciBean = ciManage.get(linkedAccountId);
        if (ciBean == null) {
            throw new CommonException("contract information not exist(linkedAccountId=" + linkedAccountId + ")");
        }

        List<CampaignAssignedBean> applyList = new ArrayList<>();
        List<CampaignGroupBean> groupList = ciBean.getCampaignGroupList();

        if (groupList == null || groupList.isEmpty()) {
            return applyList;
        }

        List<CampaignPlanBean> planList = getPlanList(mcbBean, groupList);
        if (planList.isEmpty()) {
            return applyList;
        }

        BigDecimal billingAmount = mcbBean.getBillingAmount();
        for (CampaignPlanBean planBean : planList) {
            // キャンペーン適用額 = 係数 * 請求額(税抜) + 定数 (小数点第1位で四捨五入)
            BigDecimal coefficient = planBean.getCoefficient();
            BigDecimal constant = planBean.getConstant();
            BigDecimal multiplyValue = coefficient.multiply(billingAmount);
            BigDecimal campaignAmount = multiplyValue.add(constant).setScale(0, RoundingMode.HALF_UP);

            CampaignGroupBean groupBean = groupList.stream().filter(g -> {
                return g.getCampaignPlanList().stream().filter(p -> {
                    return p.getPlanId().equals(planBean.getPlanId());
                }).findFirst().isPresent();
            }).findFirst().orElseThrow(() -> new CommonException("group is not exist")); // 通常、orElseThrowにはならない。

            CampaignAssignedBean applyBean = new CampaignAssignedBean();
            applyBean.setLinkedAccountId(linkedAccountId);
            applyBean.setYearMonth(DateUtil.toInt(targetYearMonth));
            applyBean.setAssignedPlanId(planBean.getPlanId());
            applyBean.setAssignedGroupId(groupBean.getGroupId());
            applyBean.setAssignedCampaignName(groupBean.getCampaignName());
            applyBean.setTargetAmount(billingAmount.longValueExact());
            applyBean.setCampaignAmount(campaignAmount.longValueExact());
            applyList.add(applyBean);
        }
        return applyList;
    }

    /**
     * 月次集計結果の製品名、使用タイプに該当するキャンペーンプランを、キャンペーングループから取得し、
     * キャンペーンプランリストとして、返却します。<br>
     * なお、プランIDが同じものが存在した場合は、重複させず、エラーにします。
     *
     * @param mcbBean 月次集計結果
     * @param groupList キャンペーングループリスト
     * @return キャンペーンプランリスト
     */
    private List<CampaignPlanBean> getPlanList(MonthlyCompiledBillingBean mcbBean, List<CampaignGroupBean> groupList) {
        List<String> planIdList = new ArrayList<>();
        String productName = mcbBean.getProductName();
        String usageType = mcbBean.getUsageType();
        return groupList.stream().flatMap(g -> {
            List<CampaignPlanBean> filterPlanList = null;
            List<CampaignPlanBean> planList = g.getCampaignPlanList();
            if (planList != null && !planList.isEmpty()) {
                filterPlanList = planList.stream().filter(p -> {
                    boolean isIfResult = false;
                    if (productName == null && usageType == null) {
                        isIfResult = p.getProductName() == null && p.getUsageType() == null;
                    } else if (productName != null && usageType == null) {
                        isIfResult = productName.equals(p.getProductName()) && p.getUsageType() == null;
                    } else if (productName == null && usageType != null) {
                        isIfResult = p.getProductName() == null && usageType.equals(p.getUsageType());
                    } else {
                        isIfResult = productName.equals(p.getProductName()) && usageType.equals(p.getUsageType());
                    }
                    boolean isFilter = false;
                    if (isIfResult) {
                        if (planIdList.contains(p.getPlanId())) {
                            // 同じプランIDのものが複数のグループにまたがって設定されない。
                            throw new CommonException("planId is same(multiple group)");
                        } else {
                            planIdList.add(p.getPlanId());
                            isFilter = true;
                        }
                    }
                    return isFilter;
                }).collect(Collectors.toList());
            } else {
                filterPlanList = new ArrayList<>();
            }
            return filterPlanList.stream();
        }).collect(Collectors.toList());
    }
}
